import { pushTarget, popTarget } from "./dep";
let watcherId = 0
/**
 * 渲染watcher、watch watcher、计算属性watcher
 */
export default class Watcher {
  /**
   * 
   * @param {*} vm 组件的实例
   * @param {*} exprOrFn 有可能是渲染函数、也有可能是watch 的key、计算属性的函数
   * @param {*} callback $watch 数据发生变化的回调
   * @param {*} options 配置参数：计算属性、watch配置
   */
  constructor ( vm, exprOrFn, callback, options = {} ) {
    this.watcherId = watcherId++
    this.vm = vm;

    if ( typeof exprOrFn === 'function' ) {
      // 可能渲染函数、计算属性的函数
      this.getter = exprOrFn;
    } else {
      // watch 的key
      this.getter = () => this.vm[ exprOrFn ];
    }
    this.depIds = new Set(); // 去重
    this.deps = [];
    // 计算属性默认不执行
    this.lazy = options.lazy;
    // 计算属性缓存
    this.dirty = options.lazy;
    // 主要的目的是用来记录$watch的值
    this.value = this.lazy ? undefined : this.get();
    // $watch 监听的数据发生变化的回调
    this.callback = callback;
    this.options = options;
  }

  get () {
    // 渲染watcher、watch watcher在实例化时，会立马执行get
    // 1、把当前的watcher挂载到Dep.target全局对象上
    pushTarget( this )
    // 2、如果是渲染watcher，调用渲染函数，如果有取值操作走get拦截，触发dep收集(如果值发生变化通知渲染watcher更新)
    // 2、如果watch watcher，通过取值的方式触发get拦截，触发dep收集(如果值发生变化通知watch watcher执行回调)
    let value = this.getter.call( this.vm )
    // 3、清空全局的watcher
    popTarget()
    return value
  }

  addDep ( dep ) {
    /**
     * 为什么要去重复？
     * 假设一个场景，如果一个组件同时取一个值10次，每一个次都有触发依赖收集，这样会操作一个dep收集10个相同的watcher
     * 1、空间浪费
     * 2、如果值发生变化触发dep通过更新，如果vue底层不做处理，会渲染10次
     */
    if ( !this.depIds.has( dep.depId ) ) {
      this.depIds.add( dep.depId )
      this.deps.push( dep ) // 让watcher记住dep
      dep.addSub( this ); // 让dep收集watcher
    }
  }

  // 计算属性取值触发
  evaluate () {
    // 是否有缓存(发生改变)
    if ( this.dirty ) {
      this.value = this.get()
      this.dirty = false
    }
    return this.value
  }

  // 计算属性触发收集渲染watcher，当数据改变后触发视图更新
  depend () {
    // 如果计算属性里有，name,age，也就是说一个计算属性watcher对应二个属性
    // 如果2个属性，同时去收集渲染watcher
    let l = this.deps.length;
    while ( l-- ) {
      this.deps[ l ].depend();
    }
  }

  // 事件循环解决去重
  update () {
    if ( this.lazy ) {
      // 如果是计算属性触发的更新，直接取消缓存
      this.dirty = true
    } else {
      queueWatcher( this );
    }

  }

  // 执行更新操作
  run () {
    let newValue = this.get();
    let oldValue = this.value;
    this.value = newValue;
    // watch变化后的回调
    if ( this.options.user ) {
      this.callback( newValue, oldValue );
    }
  }

}



let watcherIds = new Set();
let queue = [];
let pending = false;
function queueWatcher ( watcher ) {
  const id = watcher.watcherId;
  // 去重复，多个相同的watcher无需重复执行
  if ( !watcherIds.has( id ) ) {
    watcherIds.add( id );
    queue.push( watcher );
    if ( !pending ) {
      // vue2 里面要考虑兼容性 vue2里面会优先采用promise但是ie不支持promise 需要降级成 mutationObserver h5提供的一个方法
      // setImmediate 这个方法在ie中性能是比较好的，都不兼容fallback -> setTimeout
      Promise.resolve().then( flushShedulerQueue );
      pending = true;
    }
  }
}

function flushShedulerQueue () {
  for ( let i = 0; i < queue.length; i++ ) {
    let watcher = queue[ i ];

    watcher.run();
  }
  queue = [];
  watcherIds.clear();
  pending = false;
}